/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
/** -*- mode: c++ -*-
 * \file
 * \brief Simple binary container for communication.
 * \author Antti Maula <antti.maula@tkk.fi>
 *
 * \todo Add vector containing all 'Types' + pointer to their
 * location.  This can be used to optimize the 'Find', and also for
 * quickly checking for existence of any specific type.
 */
#ifndef _BINBAG_H_
#define _BINBAG_H_
#include <inttypes.h>

namespace gim {
  namespace binbag {

    
    /**
     * Structure of a TBinBlob entry. Contains one single data element
     * from the BinBag.
     *
     */
    struct TBinBlob {
      unsigned type : 8;     ///< Type of the element. Any free number between 0 - 255
      unsigned size : 24;    ///< Size of the data. 
      char data[0];           ///< Pointer to beginning of the data.
    }; 
    #define SIZEOF_STRUCT_TBINBLOB 4



    /** Simple binary data container for communication purposes.
     * 
     * This class can be thought as 'simplified binary XML'. It can
     * contain any number of binary elements, which can be
     * added and extracted independently. The main purpose for this
     * class is to provide easy means to package binary data 
     * for sending over GIMnet. 
     */ 
    class CBinBag
    {
    public: // Public constants
      static uint16_t KBinBag_Header_ID; ///< BinBag data indentifier.
  
    public: // Public methods
      /**
       * Default constructor.
       */
      CBinBag();

    
      /**
       * Destructor.
       */
      ~CBinBag();

    
      /** BinBag copy contructor.
       * 
       * The BinBag copy constructor is implemented as fully duplicating
       * the information from the source binbag.
       *
       * @param[in] aBinBag       Constant reference to source
       *                          BinBag.
       */
      CBinBag(const gim::binbag::CBinBag &aBinBag);

    
      /** Copy contents from another BinBag.
       *
       * This function copies the contents of source binbag to this
       * instance.  Data is completely duplicated, so no references
       * between new and old instances exist.
       * 
       * @param[in] aBinBag       Source binbag to Duplicate.
       */
      void Copy(const gim::binbag::CBinBag &aBinBag);


      /** Attempt to decode a block of data as BinBag.
       *
       * Attempt to decode a BinBag presumably contained in the provided
       * buffer.  The data is NOT COPIED per default, so the original
       * buffer must be available until the BinBag is Clear():ed or
       * destroyed.  If the Copy flag is set, the data is copied inside
       * the BinBag, and original can be deleted immediately after
       * Decode() function returns.
       * 
       * @param[in] aData         Pointer to raw data buffer to decode.
       * @param[in] aDataSize     Maximum size of data available for decoding.
       * @param[in] aCopy         If this is set to 'true', the input data will
       *                          be duplicated, releasing the original buffer immediately.
       *                          if this is set to 'false', the input data must 
       *                          be available as long as the BinBag is used.
       * @return                  Returns 'true', when the BinBag is succesfully 
       *                          decoded, and all contained elements are included
       *                          in the buffer. Returns 'false' when some error occured
       *                          during the parse process. (Not a BinBag, not enough 
       *                          data available, or other internal error. Note: 
       *                          The integrity of the contents is NOT verified, 
       *                          so this container should only be used over 
       *                          error-corrected mediums.
       */
      bool Decode(const char *aData, const int aDataSize, bool aCopy = false);


      /** Return pointer to currently constructed buffer.
       *
       * Returns a pointer to current buffer. It may be a pointer to
       * Decoded buffer, or pointer to user generated buffer (Created by
       * a sequence of '\see AddBinary()' calls). In any case, the
       * buffer is constant (read-only), and is valid only until the
       * next operation on the container BinBag instance. The pointer
       * can be user for example, when sending the generated BinBag over
       * a transport medium.
       * 
       * @return                  On success, returns a pointer to raw data buffer.
       *                          On any error, return NULL.
       */
      const char *GetBuffer(void) const;


      /** Return used buffer size.
       *
       * Returns the number of bytes stored in the internal buffer. No
       * more than this number of bytes can be read from the pointer
       * returned by the \see GetBuffer() call. 
       *
       * @return                  On success, returns the number of
       *                          bytes in buffer.
       *                          On any error, returns negative value.
       */
      int GetBufferSize(void) const;


      /** Add new Binary element.
       *
       * This functions adds a new binary element into binbag. The element
       * is appended after the last element in the current BinBag.
       *
       * \note Maximum single element size is '2^24 = 16777216 Bytes', 
       * elements larger than this cannot be stored inside BinBag. (Will
       * result in failure on add)
       *
       * @param[in] aType         Type identifier for the binary block. This
       *                          identifier is carried as-is in the 
       *                          container, and can be used by the receiver
       *                          (decoder) to identify the binary element
       *                          (without looking into its contents)
       * @param[in] aData         Pointer to data to add. The data is copied
       *                          inside the container, so the provided buffer
       *                          can be freed after this function has returned.
       * @param[in] aDataSize     Number of bytes in the databuffer.
       * @return                  Pointer to the last-added binary blob
       *                          is returned. If you are only interested
       *                          whether the operation was succesfull, check
       *                          whether the returned value is NULL or not.
       */
      const gim::binbag::TBinBlob *AddBinary(const int aType, const char *aData, const int aDataSize);


      /** Clear container.
       *
       * Clears the BinBag container. After this you can recall Decode
       * or Encode just as if this instance was just reconstructed.
       *
       * @param[in] aFreeMemory   If this flag is set to 'true', this function
       *                          will also free the internal buffer in case it exists.
       *                          This parameter defaults to 'false' in attempt to
       *                          minimize gratuitous memory (re)allocations.
       */
      void Clear(const bool aFreeMemory = false);


      /** Compress the current contents of the BinBag using zlib compression library.
       *
       * This function compresses the current contents of the container
       * using the zlib compression library implementation. This
       * function should be called on a finalized binbag (with all
       * required elements added), after which the compressed data is
       * available using the same buffer functions as usual (GetBuffer()
       * and GetBufferSize()). The compressed data may be passed as-in
       * to a BinBag Decode() function, which will detect the
       * compression and uncompress the data in place. After decoding,
       * the data is available normally through the element getters and
       * finders.
       * 
       * @param[in] aLevel Compression level to use. The range of this
       *            value is 0-9, where the 0 stands for
       *            'no-compression', and 9 stands for
       *            'best-compression'. Default value '-1' chooses a
       *            good compromise between speed and space, and
       *            corresponds to level 6.
       *
       * @return Returns 'true' if the compression was carried out
       *            succesfully, which means, that the data is available
       *            through the GetBuffer() call.  If this function
       *            returns 'false', the compression process has failed
       *            for some reason, and the contents of this binbag are
       *            intact.
       */
      bool CompressZLIB(const int &aLevel = (-1));


      /** Rewind the container.
       *
       * This functions returns the 'current pointer' inside the container
       * to the first element available. After a succesfull call to this function,
       * you can call 'GetNextBinary ()', which will again return the
       * first possible element in the container.
       * 
       * @return                  true - on success; Container contains elements
       *                          and the current element points to first
       *                          element in the container.
       *                          false - The container is empty; No elements
       *                          available.
       */
      bool Rewind();


      /** Trim the reserved buffer to match the datasize.
       *
       * This function trims the reserved buffer to match the stored data.
       * This is useful when multiple binbag containers are stored for future
       * reference, but no more operations are done for them.
       *
       */
      void TrimBuffer(void);


      /** Find a Binary by the Type field.
       *
       * This function returns the _first_ available element with specified type.
       * 'Rewind()' is first calles for the container, so current place
       * in the container is lost. If the element is found, constant pointer
       * to that element is returned.
       * 
       * @param[in] aType         Element type to search for.
       * @return                  Pointer to TBinBlob of the specified type,
       *                          or NULL if no such element was found.
       */
      const gim::binbag::TBinBlob *FindBinary(const int aType);


      /** Get next binary element from the container.
       * 
       * This function returns the next available TBinBlob element
       * from the BinBag container. If the last available element
       * is already reached, NULL is returned. To start from the
       * begin again, call Rewind ().
       * 
       * @return                  Pointer to next available TBinBlob
       *                          element. NULL is returned if there 
       *                          are no elements or last element
       *                          was already reached.
       */
      const gim::binbag::TBinBlob *GetNextBinary();


      /** Set the current iterator position.
       *
       * This function allows to change the position of the iterator. It
       * can be used to modify the result of the next call to
       * 'GetNextBinary()'. Next call to 'GetNextBinary()' will return
       * the contents of the provided pointer. (Not the next one)
       *
       * \note The provided pointer is not checked against boundary
       * limitations, so providing an invalid/modified pointer will
       * probably lead to severe problems.
       *
       * @param[in] aBlob         Set current iterator to point to 
       *                          specified TBinBlob location.
       */
      inline void SetElementIterator(const gim::binbag::TBinBlob *aBlob) {
        iElementGetIterator = const_cast<gim::binbag::TBinBlob *>(aBlob);
      }


      /** Return the number of elements in the container.
       *
       * Return the number of elements in the container.
       *
       * @return                  Number of elements in this BinBag.
       */
      inline int GetElementCount(void) const {
        return iElementCount;
      }

    
      /** Returns the real internal buffer size.
       *
       * @return                  Number of bytes reserved.
       */
      inline int GetBufferReservedSize(void) const {
        return iBufferSize;
      }
    
    private:
      ////////// Functions //////////
      /// Private function to set the container ReadOnly or not.
      void ResizeBuffer(const int aSize); ///< Resize the buffer using aSize as minimum.
      bool DoDecompressZLIB(uint8_t * &aOutput, unsigned int &aOutputSize,
                            const uint8_t *aInput, const unsigned int &aInputSize);
      bool DoCompressZLIB(uint8_t * &aOutput, unsigned int &aOutputSize,
                          const uint8_t *aInput, const unsigned int &aInputSize, const int &aLevel);
    
      CBinBag &operator=(const CBinBag &);


      ////////// Variables /////////
      bool iReadOnly; ///< Set when Decoded buffer
      bool iIsCompressed; ///< Is the container compressed?
      char *iBuffer; ///< Data container
      int iBufferSize; ///< Total allocated buffer
      int iBufferUsed; ///< Used buffer
  
      int iElementCount; ///< Number of elements in container
      gim::binbag::TBinBlob *iElementAddIterator; ///< Current element pointer
      gim::binbag::TBinBlob *iElementGetIterator; ///< Current element pointer

      static unsigned int sBufferResizes; ///< stats: How many buffer resizes?
      static unsigned int sTotalMovedBytes; ///< stats: How many moved bytes?
      static unsigned int sTotalInstanceCopied; ///< stats: How many copied instances?
    };
  }
}
#endif
